#include "sys.h"
#include "net.h"
#include "log.h"
#include "process_monitor.h"


static int socket_filter(char* path, char* d_name)
{
    char full_name[256] = { 0 };
    struct stat st;

    sprintf(full_name, "%s/%s", path, d_name);
    if( 0 == stat(full_name, &st)){
        return S_ISSOCK(st.st_mode) ? 1 : 0;
    }
    return 0;
}

static int _get_dir_contain(char* path, int (*filter)(char* path, char* d_name))
{
    int count = 0;
    DIR* dir;
    struct dirent* dent;

    dir = opendir(path);
    if (!dir) {
        return -1;
    }

    while ((dent = readdir(dir)) != NULL) {
        if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
            continue;
        if (filter) {
            count += filter(path, dent->d_name);
        } else {
            count++;
        }
    }

    closedir(dir);
    return count;
}

void get_exec_name_by_pid(int pid, char *exec_name){
    int tmpval =0;
    char stat_path[128] = {0}, tmp_name[32] = {0};
    FILE *fp = NULL;

    sprintf(stat_path, "/proc/%u/stat", pid);

    fp = fopen(stat_path, "r");
    if(!fp){
        // log_syserr("open %s failed.", stat_path);
        return;
    }

    fscanf(fp, "%d %s", &tmpval, tmp_name);
    // log_trace("tmp_name:%s", tmp_name);
    if(strlen(tmp_name) > 2){
        strncpy(exec_name, &tmp_name[1], strlen(tmp_name) - 2);
    }
    fclose(fp);

    return;
}
void get_exec_stat_by_pid(int pid, char *exec_stat){
    int tmpval =0;
    char stat_path[128] = {0}, tmp_name[32] = {0};
    FILE *fp = NULL;

    sprintf(stat_path, "/proc/%u/stat", pid);

    fp = fopen(stat_path, "r");
    if(!fp){
        log_syserr("open %s failed.", stat_path);
        return;
    }

    fscanf(fp, "%d %s %s", &tmpval, tmp_name, exec_stat);
    
    fclose(fp);

    return;
}

static int _get_sub_process_count(int pid)
{
    int count = 0;
    int tmp_pid = 0;
    char exec_name[32] = {0}, tmp_name[32] = {0};
    DIR *dir = NULL;
    struct dirent *dptr = NULL;

    get_exec_name_by_pid(pid, exec_name);
    if(strlen(exec_name) == 0){
        return 0;
    }

    dir = opendir("/proc/");
    if(!dir){
        log_syserr("opendir error.");
        return 0;
    }

    while((dptr = readdir(dir)) != NULL){
        if(strcmp(dptr->d_name, ".") == 0 || strcmp(dptr->d_name, "..") == 0){
            continue;
        }
        if((tmp_pid = atoi(dptr->d_name)) > 0){
            bzero(tmp_name, sizeof(tmp_name));
            get_exec_name_by_pid(tmp_pid, tmp_name);
            if(!strcmp(tmp_name, exec_name)){
                count++;
            }
        }

    }
    closedir(dir);
    return count;
}

int get_process_count(int pid){
    return _get_sub_process_count(pid);
}

static int _get_cpu_count(){
    FILE *fp = NULL;
    char line[128] = {0};
    int count = 0;

    fp = fopen("/proc/stat", "r");
    if(!fp){
        log_syserr("open /proc/stat error.");
        return 0;
    }

    while(fgets(line, sizeof(line), fp)){
        if(strstr(line, "cpu")){
            count++;
        }
    }
    fclose(fp);
    return count - 1;
}
static uint32_t _get_cpu_jiff(uint32_t *val, int len){
    FILE *fp = NULL;
    char tmpstr[32] = {0};
    uint32_t cpuTime = 0;

    assert(len >= 10 && val != NULL);

    fp = fopen("/proc/stat", "r");
    if(!fp){
        log_syserr("open /proc/stat/ err.");
        return 0;
    }

    fscanf(fp, "%s %u %u %u %u %u %u %u %u %u %u ", tmpstr,
            &val[0], &val[1], &val[2], &val[3], &val[4], 
            &val[5], &val[6], &val[7], &val[8], &val[9]);

    cpuTime = val[0] + val[1] + val[2] + val[3] + val[4] + val[5] + val[6] + val[7] + val[8] + val[9];

    fclose(fp);

    return cpuTime;

}
static float _get_cpu_usage(){
    #define _CPU_IDLE_IDX 3
    float usage = 0;
    uint32_t val1[10] = {0}, val2[10] = {0};
    uint32_t cpuTime1 =0 , cpuTime2 = 0, totalTime = 0, idleTime = 0;

    cpuTime1 = _get_cpu_jiff(val1, sizeof(val1)/sizeof(val1[0]));

    usleep(10000);

    cpuTime2 = _get_cpu_jiff(val2, sizeof(val2)/sizeof(val2[0]));

    totalTime = cpuTime2 - cpuTime1;
    idleTime = val2[_CPU_IDLE_IDX] - val1[_CPU_IDLE_IDX];
    usage = ((float)(totalTime - idleTime)) / totalTime * 100.0;

    return usage;
}
static void _get_process_cpu_time(char *line, long unsigned int *utime, long unsigned int *stime, long unsigned int *cutime, long unsigned int *cstime){
    char tmpstr[32] = {0};
    sscanf(line, "%*u %*s %*s %*u "               /*pid, exename state, ppid */
                            "%*u %*u %*d %*s "        /* pgid, sid, tty, tpgid */
                            "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
                            "%lu %lu "             /* utime, stime */
                            "%lu %lu %*s "         /* cutime, cstime, priority */
                            "%*s "                 /* nice */
                            "%*s %*s "             /* timeout, it_real_value */
                            "%*s "                 /* start_time */
                            "%*s "                 /* vsize */
                            "%*s "                 /* rss */
                            "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
                            "%*s %*s %*s %*s "         /*signal, blocked, sigignore, sigcatch */
                            "%*s %*s %*s %*s "         /*wchan, nswap, cnswap, exit_signal */
                            "%*d"                       /*cpu last seen on*/
        , utime, stime, cutime, cstime);
}
static uint32_t _get_single_process_cpu_jiff(int pid){
    FILE *process_fp = NULL;
    char process_stat_file[128] = {0}, line[1024] = {0};
    long unsigned int pro_val1[4] = {0}, pro_jiff = 0;

    sprintf(process_stat_file, "/proc/%u/stat", pid);

    process_fp = fopen(process_stat_file, "r");
    if(!process_fp){
        log_syserr("open %s failed.", process_stat_file);
        return 0;
    }

    fgets(line, sizeof(line), process_fp);
    _get_process_cpu_time(line, &pro_val1[0], &pro_val1[1] ,&pro_val1[2], &pro_val1[3]);
    fclose(process_fp);

    pro_jiff = pro_val1[0] + pro_val1[1] ;//+ pro_val1[2] + pro_val1[3];

    return pro_jiff;
}
static float _get_process_and_cpu_usage(int pid, float *pro_cpu_usage){

    #define _CPU_IDLE_IDX 3
    float cpu_usage = 0;
    uint32_t val1[10] = {0}, val2[10] = {0};
    uint32_t cpuTime1 =0 , cpuTime2 = 0, totalTime = 0, idleTime = 0;
    int tmp_pid = 0;
    char exec_name[32] = {0}, tmp_name[32] = {0};
    DIR *dir = NULL;
    struct dirent *dptr = NULL;
    uint32_t pro_jiff1 = 0, pro_jiff2 = 0;
    int tmp_jiff = 0;

    get_exec_name_by_pid(pid, exec_name);
    if(strlen(exec_name) == 0){
        return 0;
    }

    cpuTime1 = _get_cpu_jiff(val1, sizeof(val1)/sizeof(val1[0]));

    dir = opendir("/proc/");
    if(!dir){
        log_syserr("opendir error.");
        return 0;
    }
    while((dptr = readdir(dir)) != NULL){
        if(strcmp(dptr->d_name, ".") == 0 || strcmp(dptr->d_name, "..") == 0){
            continue;
        }
        if((tmp_pid = atoi(dptr->d_name)) > 0){
            bzero(tmp_name, sizeof(tmp_name));
            get_exec_name_by_pid(tmp_pid, tmp_name);
            if(!strcmp(tmp_name, exec_name)){
                pro_jiff1 += _get_single_process_cpu_jiff(tmp_pid);
            }
        }

    }
    closedir(dir);

    sleep(1);

    dir = opendir("/proc/");
    if(!dir){
        log_syserr("opendir error.");
        return 0;
    }
    while((dptr = readdir(dir)) != NULL){
        if(strcmp(dptr->d_name, ".") == 0 || strcmp(dptr->d_name, "..") == 0){
            continue;
        }
        if((tmp_pid = atoi(dptr->d_name)) > 0){
            bzero(tmp_name, sizeof(tmp_name));
            get_exec_name_by_pid(tmp_pid, tmp_name);
            if(!strcmp(tmp_name, exec_name)){
                pro_jiff2 += _get_single_process_cpu_jiff(tmp_pid);
            }
        }

    }
    closedir(dir);

    cpuTime2 = _get_cpu_jiff(val2, sizeof(val2)/sizeof(val2[0]));

    totalTime = cpuTime2 - cpuTime1 + 1;
    idleTime = val2[_CPU_IDLE_IDX] - val1[_CPU_IDLE_IDX];
    cpu_usage = ((float)(totalTime - idleTime)) / totalTime * 100.0;

    if(pro_cpu_usage){
        tmp_jiff = pro_jiff2 - pro_jiff1;
        tmp_jiff = tmp_jiff < 0 ? 0 : tmp_jiff;
        *pro_cpu_usage = ((float)tmp_jiff) / totalTime * 100.0;

        // log_trace("pro_jiff1:%d, pro_jiff2:%d, tmp_jiff:%d, totalTime:%d, idleTime:%d, pro_cpu_usage:%.2f", pro_jiff1, pro_jiff2, tmp_jiff, totalTime, idleTime, *pro_cpu_usage);
    }

    return cpu_usage;
}
static float _get_mem_info(int *totalMem, int *freeMem){
    FILE *fp = NULL;
    int total =0, free = 0, availableMem = 0, buffers = 0, cached = 0;
    char line[128] = {0};
    char *ptr, *delim = " ", *saveptr = NULL;
    float usage = 0;

    fp = fopen("/proc/meminfo", "r");
    if(!fp){
        log_syserr("open /proc/meminfo failed.");
        return 0;
    }
    fgets(line, sizeof(line), fp);
    ptr = strtok_r(line, delim, &saveptr);
    ptr = strtok_r(NULL, delim, &saveptr);
    total = atoi(ptr);

    fgets(line, sizeof(line), fp);
    ptr = strtok_r(line, delim, &saveptr);
    ptr = strtok_r(NULL, delim, &saveptr);
    free = atoi(ptr);

    fgets(line, sizeof(line), fp);
    if(strstr(line, "MemAvailable")){
        ptr = strtok_r(line, delim, &saveptr);
        ptr = strtok_r(NULL, delim, &saveptr);
        availableMem = atoi(ptr);

        fgets(line, sizeof(line), fp);
        ptr = strtok_r(line, delim, &saveptr);
        ptr = strtok_r(NULL, delim, &saveptr);
        buffers = atoi(ptr);

        fgets(line, sizeof(line), fp);
        ptr = strtok_r(line, delim, &saveptr);
        ptr = strtok_r(NULL, delim, &saveptr);
        cached = atoi(ptr);
    }else{
        ptr = strtok_r(line, delim, &saveptr);
        ptr = strtok_r(NULL, delim, &saveptr);
        buffers = atoi(ptr);

        fgets(line, sizeof(line), fp);
        ptr = strtok_r(line, delim, &saveptr);
        ptr = strtok_r(NULL, delim, &saveptr);
        cached = atoi(ptr);
    }
    fclose(fp);

    *totalMem = total;
    *freeMem = free;
    usage = ((float)(total - free - buffers - cached)) / total * 100.0;
    return usage;
}

static float _get_process_mem_usage(int pid, int totalMem){
    FILE *fp = NULL;
    char status_file[128] = {0};
    char line[32] = {0};
    char *ptr, *delim = " ", *saveptr = NULL;
    int proc_mem = 0;

    sprintf(status_file, "/proc/%u/status", pid);
    fp = fopen(status_file, "r");
    if(!fp){
        log_syserr("open %s failed.", status_file);
        return 0;
    }

    while(fgets(line, sizeof(line), fp)){
        if(strstr(line, "VmRSS")){
            ptr = strtok_r(line, delim, &saveptr);
            ptr = strtok_r(NULL, delim, &saveptr);
            proc_mem = atoi(ptr);
            break;
        }
    }
    fclose(fp);
    return (float)proc_mem / totalMem * 100 ;
}
void process_info(process_info_t* pinfo)
{
    char dir_path[128] = { 0 };

    get_timestring(pinfo->timestamp);

    pinfo->pid = getpid();

    pinfo->cpu_count = _get_cpu_count();
    pinfo->cpu_usage = _get_process_and_cpu_usage(pinfo->pid, &pinfo->process_cpu_usage);
    

    pinfo->mem_usage = _get_mem_info(&pinfo->mem_total, &pinfo->mem_free);
    pinfo->process_mem_usage = _get_process_mem_usage(pinfo->pid, pinfo->mem_total);

    strcpy(dir_path, "/proc/self/fd/");
    pinfo->fd_count = _get_dir_contain(dir_path, NULL);
    pinfo->socket_count = _get_dir_contain(dir_path, socket_filter);

    strcpy(dir_path, "/proc/self/task/");
    pinfo->thread_count = _get_dir_contain(dir_path, NULL);
    pinfo->sub_process_count = _get_sub_process_count(pinfo->pid);
}

void process_self_print(process_info_t *pinfo){
    log_notice("pid:%u, cpu:%.2f%%, mem:%.2f%%, thread:%d, sub pro:%d, fd:%d, socket:%d",
                pinfo->pid, pinfo->process_cpu_usage, pinfo->process_mem_usage,
                pinfo->thread_count, pinfo->sub_process_count,
                pinfo->fd_count, pinfo->socket_count);
}
void process_info_print(process_info_t* pinfo)
{
    log_trace("========== Process Info ==========");
    log_trace("pid:%d", pinfo->pid);
    log_trace("cpu count:%d", pinfo->cpu_count);
    log_trace("cpu usage:%.2f%%", pinfo->cpu_usage);
    log_trace("process cpu usage:%.2f%%", pinfo->process_cpu_usage);
    log_trace("total Mem:%d KB", pinfo->mem_total);
    log_trace("free Mem:%d KB", pinfo->mem_free);
    log_trace("mem usage:%.2f%%", pinfo->mem_usage);
    log_trace("process mem usage:%.2f%%", pinfo->process_mem_usage);
    log_trace("fd:%d", pinfo->fd_count);
    log_trace("socket:%d", pinfo->socket_count);
    log_trace("thread:%d", pinfo->thread_count);
    log_trace("sub_process_count:%d", pinfo->sub_process_count);
    log_trace("==================================");
}

process_threshold_t* process_threshold_alloc()
{
    process_threshold_t* threshold = NULL;

    threshold = calloc(1, sizeof(process_threshold_t));
    if (!threshold) {
        return NULL;
    }
    return threshold;
}
int process_threshold_check(process_info_t* pinfo)
{
    if (pinfo->threshold == NULL)
        return 1;

    if ((pinfo->fd_count > pinfo->threshold->fd_count && pinfo->threshold->fd_count != 0)
        || (pinfo->socket_count > pinfo->threshold->socket_count && pinfo->threshold->socket_count != 0)
        || (pinfo->thread_count > pinfo->threshold->thread_count && pinfo->threshold->thread_count != 0)) {
        return 1;
    }

    return 0;
}
void* process_monitor(void* arg)
{
    int interval = 5;
    process_info_t *pro_info = NULL;

    if(!arg)
        return NULL;

    pro_info = (process_info_t*)arg;
  
    while (1) {
        sleep(interval);
        process_info(pro_info);
        if (process_threshold_check(pro_info)) {
            process_info_print(pro_info);
        }
    }
}